﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This class contains a bunch of static/global methods for managing the tracing (replaying) of the program execution.
	/// </summary>
	public static class TracingSystem
	{
		/// <summary>
		/// The StreamWriter that the trace is written to.
		/// </summary>
		private static System.IO.StreamWriter s_tlogstream;

		/// <summary>
		/// The StreamWriter that the use/mod trace is written to.
		/// </summary>
		private static System.IO.StreamWriter s_umlogstream;

		private static bool s_baselogging = false;
		private static bool s_usemodlogging = false;
		private static bool s_datalogging = false;

		/// <summary>
		/// Initialize the tracing system
		/// </summary>
		internal static void InitizeTracing(string logfile)
		{
			if(logfile != null)
			{
				string tlogf = System.IO.Path.Combine(logfile, "exelog.txt");
				s_tlogstream = new System.IO.StreamWriter(tlogf, false);

				string umlogf = System.IO.Path.Combine(logfile, "umlog.txt");
				s_umlogstream = new System.IO.StreamWriter(umlogf, false);

				///////
				//for debugging reset later
#if DEBUG
				s_tlogstream.AutoFlush = true;
				s_umlogstream.AutoFlush = true;
#endif
			}
		}

		/// <summary>
		/// Turn on use mod logging.
		/// </summary>
		public static void EnableUseModLogging()
		{
			s_baselogging = true;
			s_usemodlogging = true;
		}

		/// <summary>
		/// Turn on data history logging.
		/// </summary>
		public static void EnableDataLogging()
		{
			s_baselogging = true;
			s_datalogging = true;
		}

		////////////////////////////////////
		//Control flow logging

		/// <summary>
		/// Log the call of a given builtin method
		/// </summary>
		internal static void LogBuiltinMethodCallEnter(IRInvokeIdentity callinto)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("CallBuiltinEnter Time={0} Line={1} Key={2} Name={3}::{4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, callinto.InvIDStableKey, callinto.DefInClass.BaseTypeName, callinto.InvSig.InvokeName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the call of a given builtin method
		/// </summary>
		internal static void LogBuiltinMethodCallExit(IRInvokeIdentity callinto)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("CallBuiltinExit Time={0} Line={1} Key={2} Name={3}::{4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, callinto.InvIDStableKey, callinto.DefInClass.BaseTypeName, callinto.InvSig.InvokeName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the call of a given method
		/// </summary>
		internal static void LogMethodCall(IRInvokeIdentity callinto)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("CallInto Time={0} Line={1} Key={2} Name={3}::{4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, callinto.InvIDStableKey, callinto.DefInClass.BaseTypeName, callinto.InvSig.InvokeName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the return from the given method
		/// </summary>
		internal static void LogMethodReturn(IRInvokeIdentity callexit)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("CallReturn Time={0} Line={1} Key={2} Name={3}::{4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, callexit.InvIDStableKey, callexit.DefInClass.BaseTypeName, callexit.InvSig.InvokeName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log a branch taken
		/// </summary>
		internal static void LogJump(long fromblock, long toblock)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("Jump Time={0} From={1} To={2}", GlobalInterpreterState.TCurrentTime, fromblock, toblock);
			s_tlogstream.WriteLine(fs);
		}

		////////////////////////////////////
		//Memory event logging

		/// <summary>
		/// Log the creation of a home location at the given address for the named static field
		/// </summary>
		internal static void LogAddNewStaticHomeAlloc(IRStaticField sf, int address)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("AllocStaticHome Time={0} Line={1} Address={2} Key={3} Name={4}::{5}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, sf.RefID, sf.DefinedInClass.BaseTypeName, sf.FieldName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the creation of a home location at the given address for the named variable
		/// </summary>
		internal static void LogAddNewLocalVarHomeAlloc(IRVarName v, int address)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("AllocVarHome Time={0} Line={1} Address={2} Key={3} Name={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, v.RefID, v.VarName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the allocation of the given object type at the given address on the heap
		/// </summary>
		internal static void LogNewHeapAllocation(IRClassType oftype, int address)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("AllocObject Time={0} Line={1} Address={2} Key={3} Name={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, oftype.TypeID, oftype.BaseTypeName);
			s_tlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the removal of the home location at the given address for the named variable
		/// </summary>
		internal static void LogRemoveLocalVarHomeAlloc(IRVarName v, int address)
		{
			if(!s_baselogging)
				return;

			string fs = String.Format("RemoveVarHome Time={0} Line={1} Address={2} Key={3} Name={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, v.RefID, v.VarName);
			s_tlogstream.WriteLine(fs);
		}

		////////////////////////////////////
		//Use/Mod logging

		/// <summary>
		/// Log a use of the value stored at the given position in a given memory location.
		/// </summary>
		internal static void LogUseFromLocation(int address, IRBasicAccessPath bap, IRType locationtype)
		{
			if(!s_usemodlogging)
				return;

			IRType logtype = (bap.IsEmptyAccessPath ? locationtype : bap.PathLast.FieldType);
			
			if(logtype is IRPrimitiveNumericType || logtype is IREnumType || logtype is IRPtrType || logtype is IRReferenceType)
			{
				string fs = String.Format("Read Time={0} Line={1} Address={2} Loc={3} Path={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString());
				s_umlogstream.WriteLine(fs);
			}
			else
			{
				IRStructType stt = (IRStructType)bap.PathLast.FieldType;
				for(int i = 0; i < stt.TypeDescription.AllTerminalAccessPaths.Count; ++i)
				{
					IRBasicAccessPath tpp = stt.TypeDescription.AllTerminalAccessPaths[i];
					string fs = String.Format("Read Time={0} Line={1} Address={2} Loc={3} Path={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString());
					s_umlogstream.WriteLine(fs);
				}
			}
		}

		/// <summary>
		/// Log a modification of the value stored at the given position in a given memory location.
		/// </summary>
		internal static void LogModToLocation(int address, IRBasicAccessPath bap, IRType locationtype)
		{
			if(!s_usemodlogging)
				return;

			IRType logtype = (bap.IsEmptyAccessPath ? locationtype : bap.PathLast.FieldType);

			if(logtype is IRPrimitiveNumericType || logtype is IREnumType || logtype is IRPtrType || logtype is IRReferenceType)
			{
				string fs = String.Format("Write Time={0} Line={1} Address={2} Loc={3} Path={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString());
				s_umlogstream.WriteLine(fs);
			}
			else
			{
				IRStructType stt = (IRStructType)bap.PathLast.FieldType;
				for(int i = 0; i < stt.TypeDescription.AllTerminalAccessPaths.Count; ++i)
				{
					IRBasicAccessPath tpp = stt.TypeDescription.AllTerminalAccessPaths[i];
					string fs = String.Format("Write Time={0} Line={1} Address={2} Loc={3} Path={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString());
					s_umlogstream.WriteLine(fs);
				}
			}
		}

		////////////////////////////////////
		//Use/Mod logging for eval stack manipulation

		/// <summary>
		/// Log a push to the eval stack
		/// </summary>
		internal static void LogEvalStackValues(EvalStack evs)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("EValue Time={0} Line={1} {2}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, evs.EmitForTrace());
			s_tlogstream.WriteLine(fs);
		}

		////////////////////////////////////
		//Use/Mod logging for value overwritting

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldNumeric(int address, IRBasicAccessPath bap, NumericValue nv)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldNum Time={0} Line={1} Address={2} Loc={3} Path={4} Value={5}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString(), nv.EmitForTrace());
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldPointer(int address, IRBasicAccessPath bap, PtrValue pv)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldPtr Time={0} Line={1} Address={2} Loc={3} Path={4} Value={5}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString(), pv.EmitForTrace());
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldReference(int address, IRBasicAccessPath bap, ReferenceValue rv)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldRef Time={0} Line={1} Address={2} Loc={3} Path={4} Value={5}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString(), rv.EmitForTrace());
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldComposite(int address, IRBasicAccessPath bap, CompositeValueEntry[] cve)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldComp Time={0} Line={1} Address={2} Loc={3} Path={4} Value={5}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, bap.PathID, bap.FieldOnlyDisplayString(), CompositeValue.EmitForTrace(cve));
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldNumeric_InArray(int address, int rindex, NumericValue nv)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldNumArray Time={0} Line={1} Address={2} RawPos={3} Value={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, rindex, nv.EmitForTrace());
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldPointer_InArray(int address, int rindex, PtrValue pv)
		{
			if(!s_datalogging)
				return;

			string fs = String.Format("OldPtrArray Time={0} Line={1} Address={2} RawPos={3} Value={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, rindex, pv.EmitForTrace());
			s_umlogstream.WriteLine(fs);
		}

		/// <summary>
		/// Log the value at the given location (which is about to be overwritten).
		/// </summary>
		internal static void LogOldComposite_InArray(int address, int rindex, int length, SimpleMemoryValue[] sva)
		{
			if(!s_datalogging)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("[");
			for(int i = 0; i < sva.Length; ++i)
			{
				if(i != 0)
					sb.Append(",");
				sb.Append(sva[i].EmitForTrace());
			}
			sb.Append("]");

			string fs = String.Format("OldCompArray Time={0} Line={1} Address={2} RawPos={3} Value={4}", GlobalInterpreterState.TCurrentTime, GlobalInterpreterState.TCurrentLine, address, rindex, sb.ToString());
			s_umlogstream.WriteLine(fs);
		}

		////////////////////////////////////
		//Trace statement manipulation

		/// <summary>
		/// A static array containing a space
		/// </summary>
		private static char[] s_spacearray = new char[] { ' ' };

		/// <summary>
		/// Parse a log entry into a set of key/value pairs.
		/// </summary>
		internal static Dictionary<string, string> ParseKeyValuePairs(string logentry)
		{
			string[] vals = logentry.Trim().Split(s_spacearray);

			Dictionary<string, string> kvpd = new Dictionary<string, string>();
			for(int i = 0; i < vals.Length; ++i)
			{
				string s = vals[i];

				int eqpos = s.IndexOf('=');
				string keystr = s.Substring(0, eqpos);
				string valstr = s.Substring(eqpos + 1);

				kvpd.Add(keystr, valstr);
			}

			return kvpd;
		}
	}
}

